﻿#include "address_book.h"
// C/C++ 学习网站：https://legacy.cplusplus.com 注意旧版本（搜索）和新版本

void clean_buffer()
{
    while (getchar() != '\n');
}// 清空输入缓冲区

void contact_init(contact* p)
{ // 初始化联系人
    assert(p); // 断言，如果p为空，则程序终止
    //p->flag = 0; // 静态版本的联系人标志位
    p->save_flag = 1;
    p->count = 0;
    //memset(p->data, 0, sizeof(p->data)); // 静态版本的联系人数组
    p->data = (info*)calloc(DEFAULT_SIZE, sizeof(info)); // 动态分配内存
    if (!p->data) // 内存分配失败
    {
        printf("contact_init联系人数据存储内存分配失败！%s\n", strerror(errno));
        exit(1); // 退出程序
    }
    p->size = DEFAULT_SIZE; // 初始化数组大小
    printf("联系人数组初始化成功，当前大小为 %d，已有 %d 个联系人\n", p->size, p->count);
}

static int is_zero(const contact* p)
{ // 判断联系人数量是否为 0
    if (!(p->count)) // 联系人数量为 0
    {
        printf("没有可用的联系人\n");
        return 1;
    }
    else
        return 0;
}

static int search_contact(const contact* p, const char* name)
{ // 查找联系人位置。static 修饰函数表示函数不能被其他文件引用
    assert(p && name); // 断言，如果p为空，则程序终止
    int i;
    for (i = 0; i < p->count; ++i)
        if (0 != p->data[i].name && name &&
            strcmp(p->data[i].name, name) == 0)
            return i;
    return -1;
}

void show_contact(const contact* p)
{ // 打印所有联系人
    assert(p); // 断言，如果p为空，则程序终止
    if (is_zero(p))
        return;
    else
    {
        printf("%52s\t年龄\t性别\t%18s\t住址\n", "姓名", "电话");
        for (int i = 0; i < p->count; ++i)
            printf("%50s\t%d\t%s\t%18s\t%s\n",
                p->data[i].name, p->data[i].age, p->data[i].sex,
                p->data[i].phone, p->data[i].address);
    }
}

static int add_data_memory(contact* p)
{ // 动态分配内存
    assert(p); // 断言，如果p为空，则程序终止
    if (p->count >= p->size)
    {
        printf("%zd\t%zd\n", sizeof(info), (p->size + ADD_SIZE) * sizeof(info));
        info* new_data = (info*)realloc(p->data, (p->size + ADD_SIZE) * sizeof(info));
        if (!new_data) // 内存分配失败
        {
            printf("add_data_memory空间分配失败，不能添加联系人！%s\n", strerror(errno));
            return -1; // 退出程序
        }
        else
        {
            p->data = new_data;
            p->size += ADD_SIZE;
            //free(new_data); // 这里不能释放 new_data 内存，因为 p->data == new_data
            new_data = NULL; // 置空 new_data 指针
            printf("联系人数组扩容成功，当前大小为 %d，已有 %d 个联系人\n", p->size, p->count);
        }
    }
    return p->count;
}

void add_contact(contact* p)
{ // 添加联系人
    assert(p); // 断言，如果p为空，则程序终止
    /* 静态的版本
    if (p->count >= MAX_CONTACT_COUNT) // 联系人数量达到最大值
    {
        printf("联系人数量达到最大值！\n");
        p->flag = 1;
        return;
    }*/
    //else if (!p->flag) // 静态版本，联系人数量没有达到最大值
    if (-1 != add_data_memory(p)); // 动态版本，联系人数量达到最大值
    {
        printf("请按照提示输入信息：\n");
        printf("请输入联系人姓名：");
        char* name = (char*)malloc(sizeof(char) * 50);
        int name_len = scanf("%50s", name); // 限制输入姓名长度为 50
        clean_buffer();
        int i = search_contact(p, name);
        if (0 != p->data[i].name && name &&
            strcmp(p->data[i].name, name) == 0)
        {
            printf("联系人 %s 已存在！\n", name);
            free(name);
            return;
        }
        if (name && *name)
            strcpy(p->data[p->count].name, name);
        printf("请输入联系人年龄：");
        int input_age = scanf("%d", &(p->data[p->count].age));
        clean_buffer();
        printf("请输入联系人性别：");
        int input_gender = scanf("%10s", p->data[p->count].sex);
        clean_buffer();
        printf("请输入联系人电话：");
        int input_phone = scanf("%18s", p->data[p->count].phone);
        clean_buffer();
        printf("请输入联系人住址：");
        int input_address = scanf("%200s", p->data[p->count].address);
        clean_buffer();
        ++p->count;
        printf("联系人添加成功！\n");
        p->save_flag = 0; // 标记联系人信息未保存
    }
}

void select_contact(const contact* p)
{ // 查询单个联系人
    do
    {
        printf("请输入要查询的联系人姓名：");
        char* select_name = (char*)malloc(sizeof(char) * 50);
        int select_len = scanf("%s", select_name);
        clean_buffer();
        int i = search_contact(p, select_name);
        if (0 != p->data[i].name && select_name &&
            strcmp(p->data[i].name, select_name) == 0)
        {
            printf("在 %d 位置找到联系人：%s；联系人相关信息如下：\n",
                i + 1, p->data[i].name);
            printf("%52s\t年龄\t性别\t%18s\t住址\n", "姓名", "电话");
            printf("%50s\t%d\t%s\t%18s\t%s\n",
                p->data[i].name, p->data[i].age, p->data[i].sex,
                p->data[i].phone, p->data[i].address);
            break;
        }
        else
        {
            printf("联系人%s不存在！\n", select_name);
            break;
        }
        free(select_name);
        return;
    } while (1);
}

static void in_func_show(contact* p)
{ // 修改联系人过程中显示所有联系人信息
    do
    {
        printf("是否显示所有联系人信息(Y/N)>");
        char show_all = 'Y';
        int show_num = scanf("%c", &show_all);
        clean_buffer();
        if (show_all == 'Y' || show_all == 'y')
        {
            show_contact(p);
            return;
        }
        else if (show_all == 'N' || show_all == 'n')
        {
            printf("已退出联系人显示！\n");
            return;
        }
        else
        {
            printf("输入错误！请重新选择！\n");
            continue;
        }
    } while (1);
}

void delete_contact(contact* p)
{ // 删除单个联系人
    assert(p); // 断言，如果p为空，则程序终止
    if (is_zero(p)) // 联系人数量为 0
        return;
    else
    {
    again:
        printf("请输入要删除的联系人姓名：");
        char delete_name[50];
        int delete_len = scanf("%s", delete_name);
        clean_buffer();
        int i = search_contact(p, delete_name);
        if (NULL != p->data[i].name && NULL != delete_name &&
            strcmp(p->data[i].name, delete_name) == 0)
        {
        dodelete:
            printf("在 %d 位置找到联系人找到联系人：%s；是否删除该联系人(Y/N)>",
                i + 1, p->data[i].name);
            char input_char;
            int input_num = scanf("%c", &input_char);
            clean_buffer();
            if (input_char == 'Y' || input_char == 'y')
            {
                printf("联系人删除成功！以下是删掉的联系人的信息：\n");
                printf("%52s\t年龄\t性别\t%18s\t住址\n", "姓名", "电话");
                printf("%50s\t%d\t%s\t%18s\t%s\n",
                    p->data[i].name, p->data[i].age, p->data[i].sex,
                    p->data[i].phone, p->data[i].address);
                int j;
                for (j = i; j < p->count - 1; ++j)
                    p->data[j] = p->data[j + 1];
                memset(&p->data[p->count - 1], 0, sizeof(info));
                --p->count;
                p->save_flag = 0; // 标记联系人信息未保存
            }
            else if (input_char == 'N' || input_char == 'n')
                printf("取消删除联系人！\n");
            else
            {
                printf("输入错误！请重新选择！\n");
                goto dodelete;
            }
        }
        else
        {
            printf("联系人%s不存在；请重新输入！\n", delete_name);
            in_func_show(p);
            while (1)
            {
                printf("是否继续删除联系人(Y/N)>");
                char cont = 'N';
                int cont_num = scanf("%c", &cont);
                clean_buffer();
                if (cont == 'Y' || cont == 'y')
                    goto again;
                else if (cont == 'N' || cont == 'n')
                    return;
                else
                {
                    printf("输入错误！请重新选择！\n");
                    continue;
                }
            }
        }
    }
}

void modify_contact(contact* p)
{ // 修改联系人信息
    assert(p); // 断言，如果p为空，则程序终止
    if (is_zero(p)) // 联系人数量为 0
        return;
    else
    {
        while (1)
        {
            printf("请输入要修改的联系人姓名：");
            char* modify_name = (char*)malloc(sizeof(char) * 50);
            int modify_len = scanf("%s", modify_name);
            clean_buffer();
            int i = search_contact(p, modify_name);
            if (0 != p->data[i].name && modify_name &&
                strcmp(p->data[i].name, modify_name) == 0)
            {
            reselect:
                printf("在 %d 位置找到联系人：%s；是否修改该联系人(Y/N)>",
                    i + 1, p->data[i].name);
                char input_char;
                int input_num = scanf("%c", &input_char);
                clean_buffer();
                while (input_char == 'Y' || input_char == 'y')
                {
                    printf("请按照提示输入需要修改的联系人信息：\n");
                    printf("1.修改联系人姓名：\n");
                    printf("2.修改联系人年龄：\n");
                    printf("3.修改联系人性别：\n");
                    printf("4.修改联系人电话：\n");
                    printf("5.修改联系人住址：\n");
                    printf("6.修改整个联系人信息：\n");
                    printf("0.退出修改联系人：\n");
                    printf("请输入需要修改的选项：");
                    char select_char;
                    int select_num = scanf("%c", &select_char);
                    clean_buffer();
                    switch (select_char)
                    {
                    case '0':
                        printf("已退出修改联系人！\n");
                        return;
                    case '1':
                        printf("请输入新的联系人姓名：");
                        int input_name = scanf("%s", p->data[i].name);
                        printf("联系人姓名修改成功！\n");
                        break;
                    case '2':
                        printf("请输入新的联系人年龄：");
                        int input_age = scanf("%d", &(p->data[i].age));
                        break;
                    case '3':
                        printf("请输入新的联系人性别：");
                        int input_gender = scanf("%s", p->data[i].sex);
                        printf("联系人性别修改成功！\n");
                        break;
                    case '4':
                        printf("请输入新的联系人电话：");
                        int input_phone = scanf("%s", p->data[i].phone);
                        printf("联系人电话修改成功！\n");
                        break;
                    case '5':
                        printf("请输入新的联系人住址：");
                        int input_address = scanf("%s", p->data[i].address);
                        printf("联系人住址修改成功！\n");
                        break;
                    case '6':
                        printf("请输入新的联系人姓名：");
                        int get_name = scanf("%s", p->data[i].name);
                        clean_buffer();
                        printf("请输入新的联系人年龄：");
                        int get_age = scanf("%d", &(p->data[i].age));
                        clean_buffer();
                        printf("请输入新的联系人性别：");
                        int get_gender = scanf("%s", p->data[i].sex);
                        clean_buffer();
                        printf("请输入新的联系人电话：");
                        int get_phone = scanf("%s", p->data[i].phone);
                        clean_buffer();
                        printf("请输入新的联系人住址：");
                        int get_address = scanf("%s", p->data[i].address);
                        printf("联系人信息修改成功！\n");
                        p->save_flag = 0; // 标记联系人信息未保存
                        break;
                    default:
                        printf("输入错误！请重新选择！\n");
                        goto reselect;
                    }
                    break;
                }
                if (input_char == 'N' || input_char == 'n')
                {
                    printf("取消修改联系人！\n");
                    return;
                }
                else if (input_char != 'Y' && input_char != 'y')
                {
                    printf("输入错误，请重新选择！\n");
                    goto reselect;
                }
            }
            else
            {
                printf("联系人%s不存在；请重新输入！\n", modify_name);
                in_func_show(p);
                continue;
            }
            free(modify_name);
            /*修改选择:
                printf("是否继续修改联系人(Y/N)>");
                char input_continue = 'N';
                clean_buffer(); // 清空输入缓冲区的换行符 '\n'
                int continue_num = scanf("%c", &input_continue);
                clean_buffer();
                if (input_continue == 'N' || input_continue == 'n')
                {
                    printf("已退出联系人修改！\n");
                    return;
                }
                else if (input_continue == 'Y' || input_continue == 'y')
                    continue; // 回到最前面的 while (1) 循环，使用 goto 避免使用递归
                else
                {
                    printf("输入错误！请重新选择！\n");
                    modify_show(p);
                    goto 修改选择;
                } */
            break;
        }
    }
}

static int cmp_struct_name_up(const void* elem1, const void* elem2)
{ // 自定义比较函数，用于 info 结构体数组排序，按照 name 升序排序
    return strcmp(((info*)elem1)->name, ((info*)elem2)->name);
}

static int cmp_struct_name_down(const void* elem1, const void* elem2)
{ // 自定义比较函数，用于 info 结构体数组排序，按照 name 降序排序
    return strcmp(((info*)elem2)->name, ((info*)elem1)->name);
}

static int cmp_struct_age_up(const void* elem1, const void* elem2)
{ // 自定义比较函数，用于 info 结构体数组排序，按照 age 升序排序
    return ((info*)elem1)->age - ((info*)elem2)->age;
}

static int cmp_struct_age_down(const void* elem1, const void* elem2)
{ // 自定义比较函数，用于 info 结构体数组排序，按照 age 降序排序
    return ((info*)elem2)->age - ((info*)elem1)->age;
}

static int cmp_struct_sex_up(const void* elem1, const void* elem2)
{ // 自定义比较函数，用于 info 结构体数组排序，按照 sex 升序排序
    return strcmp(((info*)elem1)->sex, ((info*)elem2)->sex);
}

static int cmp_struct_sex_down(const void* elem1, const void* elem2)
{ // 自定义比较函数，用于 info 结构体数组排序，按照 sex 降序排序
    return strcmp(((info*)elem2)->sex, ((info*)elem1)->sex);
}

void sort_contact(contact* p)
{ // 排序联系人
    assert(p); // 断言，如果p为空，则程序终止
    if (is_zero(p)) // 联系人数量为 0
        return;
    else if (p->count == 1) // 联系人数量为 1，无需排序
    {
        printf("联系人数量为 1，无需排序！\n");
        return;
    }
    else
    {
    reselect:
        printf("请选择排序方式：\n");
        printf("1.按照姓名排序：\n");
        printf("2.按照年龄排序：\n");
        printf("3.按照性别排序：\n");
        printf("0.退出排序：\n");
        printf("请输入排序方式：");
        int select_sort;
        int select_num = scanf("%d", &select_sort);
        clean_buffer();
        char up = 'Y';
        switch (select_sort)
        {
        case 0:
            printf("已退出联系人排序！\n");
            return;
        case 1:
            printf("是否升序，输入 N 则降序排序(Y/N)>");
            int name_up_num = scanf("%c", &up);
            clean_buffer();
            if (up == 'Y' || up == 'y')
                qsort(p->data, p->count, sizeof(info),
                    cmp_struct_name_up); // 按照 name 升序排序
            else if (up == 'N' || up == 'n')
                qsort(p->data, p->count, sizeof(info),
                    cmp_struct_name_down); // 按照 name 降序排序
            else
            {
                printf("输入错误！请重新选择！\n");
                goto reselect;
            }
            break;
        case 2:
            printf("是否升序(Y/N)>");
            int age_up_num = scanf("%c", &up);
            clean_buffer();
            if (up == 'Y' || up == 'y')
                qsort(p->data, p->count, sizeof(info),
                    cmp_struct_age_up); // 按照 age 升序排序
            else if (up == 'N' || up == 'n')
                qsort(p->data, p->count, sizeof(info),
                    cmp_struct_age_down); // 按照 age 降序排序
            else
            {
                printf("输入错误！请重新选择！\n");
                goto reselect;
            }
            break;
        case 3:
            printf("是否升序(Y/N)>");
            int sex_up_num = scanf("%c", &up);
            clean_buffer();
            if (up == 'Y' || up == 'y')
                qsort(p->data, p->count, sizeof(info),
                    cmp_struct_sex_up); // 按照 sex 升序排序
            else if (up == 'N' || up == 'n')
                qsort(p->data, p->count, sizeof(info),
                    cmp_struct_sex_down); // 按照 sex 降序排序
            else
            {
                printf("输入错误！请重新选择！\n");
                goto reselect;
            }
            break;
        default:
            printf("输入错误！请重新选择！\n");
            goto reselect;
        }
        printf("联系人排序成功！所有联系人信息如下：\n");
        show_contact(p);
        p->save_flag = 0; // 标记联系人信息未保存
    }
}

void save_contact(contact* p)
{ // 保存所有联系人信息到文件
    assert(p); // 断言，如果p为空，则程序终止
    if (is_zero(p))
    {
        printf("保存联系人到文件失败！\n");
        return;
    }
    printf("请输入保存通信录的文件名：");
    int file_len = scanf("%s", filename);
    clean_buffer();
    char really_name[205];
    fp = fopen(strcat(strcpy(really_name, filename), ".txt"), "a+");
    fseek(fp, 0, SEEK_END); // 定位到文件末尾
    //printf("%d\n", ftell(fp)); // 输出文件指针位置
    if (fp == NULL)
    {
        printf("文件打开失败！\n");
        return;
    }
    else if (ftell(fp) == 0)
        fprintf(fp, "每行数据从左到右依次为：姓名，年龄，性别，电话，住址\n");
    int i;
    for (i = 0; i < p->count; ++i)
    {
        fprintf(fp, "%s\t%d\t%s\t%s\t%s\n", p->data[i].name, p->data[i].age,
            p->data[i].sex, p->data[i].phone, p->data[i].address);
        printf("已保存联系人 %s 到文件 %s\n", p->data[i].name, really_name);
    }
    fclose(fp);
    fp = NULL; // 关闭文件指针
    p->save_flag = 1; // 标记联系人信息已保存
}

void import_contact(contact* p)
{ // 从文件导入联系人信息
    assert(p); // 断言，如果p为空，则程序终止
    char imt[301];
    printf("请输入要导入的文件名（不含后缀）：");
    int imt_len = scanf("%300s", imt);
    imt[imt_len] = '\0'; // 字符串末尾添加结束符
    clean_buffer();
    char really_name[306];
    fp = fopen(strcat(strcpy(really_name, imt), ".txt\0"), "r");
    if (fp == NULL)
    {
        perror("文件打开失败！");
        return;
    }
    strcpy(imt, fgets(imt, 300, fp)); // 读取第一行
    int ret_num;
    if (!strcmp(imt, "每行数据从左到右依次为：姓名，年龄，性别，电话，住址\n"));// 判断是否为注释
    else if (p->size > p->count) // 不为注释，且联系人数量未超过最大值
        ret_num = sscanf(imt, "%s %d %s %s %s", &p->data[p->count].name,
            &p->data[p->count].age, &p->data[p->count].sex, &p->data[p->count].phone,
            &p->data[p->count].address); // 导入第一行联系人信息
    while (!feof(fp)) // 循环读取文件，直到文件结束
    {
        char* imt_in;
        if (NULL != (imt_in = fgets(imt, 300, fp)))
        {
            strcpy(imt, imt_in);
            if (-1 == add_data_memory(p)) // 联系人数量已超过最大值，扩容
                return; // 扩容失败，退出
            else
                ret_num = sscanf(imt_in, "%s %d %s %s %s",
                    &p->data[p->count].name, &p->data[p->count].age, &p->data[p->count].sex,
                    &p->data[p->count].phone, &p->data[p->count].address); // 每次导入一行
            printf("已导入联系人：%s\n", p->data[p->count].name);
            ++p->count; // 联系人数量加 1
            p->save_flag = 0; // 标记联系人信息未保存
        }
        else
            break; // 文件结束，退出循环
    }
    fclose(fp);
    fp = NULL; // 关闭文件指针
}
